package mysql

import (
	"net"

	"code.byted.org/bytertc/whiteboard_server/biz/dal/ddl"

	"code.byted.org/gopkg/logs/v2/log"
	"code.byted.org/gorm/bytedgorm"
	"code.byted.org/videoarch/universal_utils_go/event_collector/tracer"
	sqle "github.com/dolthub/go-mysql-server"
	"github.com/dolthub/go-mysql-server/auth"
	"github.com/dolthub/go-mysql-server/memory"
	"github.com/dolthub/go-mysql-server/server"
	"github.com/dolthub/go-mysql-server/sql"
	"github.com/dolthub/go-mysql-server/sql/information_schema"
	"github.com/spf13/cast"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var port int

func init() {
	engine := sqle.NewDefault(sql.NewDatabaseProvider(
		createTestDatabase(),
		information_schema.NewInformationSchemaDatabase(),
	))
	addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
	if err != nil {
		panic(err)
	}
	l, err := net.Listen("tcp", addr.String())
	if err != nil {
		panic(err)
	}

	port = l.Addr().(*net.TCPAddr).Port
	l.Close()
	log.V1.Info("port:%+v", port)
	config := server.Config{
		Protocol: "tcp",
		Address:  "localhost:" + cast.ToString(port),
		Auth:     auth.NewNativeSingle("user", "password", auth.AllPermissions),
	}

	s, err := server.NewDefaultServer(config, engine)
	if err != nil {
		panic(err)
	}
	go func() {
		_ = s.Start()
	}()

	tracer.InitTracer()
}

// nolint
func createTestDatabase() *memory.Database {
	db := memory.NewDatabase("test")

	db.AddTable(ddl.TableObjects, memory.NewTable(ddl.TableObjects, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true, Source: ddl.TableObjects},
		{Name: "app_id", Type: sql.Text, Source: ddl.TableObjects},
		{Name: "room_id", Type: sql.Text, Source: ddl.TableObjects},
		{Name: "object_id", Type: sql.Text, Source: ddl.TableObjects},
		{Name: "board_id", Type: sql.Int32, Source: ddl.TableObjects},
		{Name: "page_id", Type: sql.Text, Source: ddl.TableObjects},
		{Name: "action_count", Type: sql.Int32, Source: ddl.TableObjects},
		{Name: "initialized", Type: sql.Boolean, Source: ddl.TableObjects},
		{Name: "version", Type: sql.Int32, Source: ddl.TableObjects},
		{Name: "data", Type: sql.MediumBlob, Source: ddl.TableObjects},
		{Name: "deleted_at", Type: sql.Timestamp, Nullable: true, Source: ddl.TableObjects},
	}))

	db.AddTable(ddl.TablePackets, memory.NewTable(ddl.TablePackets, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true},
		{Name: "app_id", Type: sql.Text},
		{Name: "room_id", Type: sql.Text},
		{Name: "board_id", Type: sql.Int32},
		{Name: "action_count", Type: sql.Int64},
		{Name: "start_time", Type: sql.Uint64},
		{Name: "version", Type: sql.Int32},
		{Name: "data", Type: sql.MediumBlob},
	}))

	boardsTable := memory.NewTable(ddl.TableBoards, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true, Source: ddl.TableBoards},
		{Name: "app_id", Type: sql.Text, Source: ddl.TableBoards},
		{Name: "room_id", Type: sql.Text, Source: ddl.TableBoards},
		{Name: "board_id", Type: sql.Int32, Source: ddl.TableBoards},
		{Name: "status", Type: sql.Int32, Source: ddl.TableBoards},
		{Name: "current_page", Type: sql.Text, Source: ddl.TableBoards},
		{Name: "current_step", Type: sql.Int32, Source: ddl.TableBoards},
		{Name: "created_at", Type: sql.Timestamp, Source: ddl.TableBoards},
		{Name: "updated_at", Type: sql.Timestamp, Source: ddl.TableBoards},
		{Name: "control_count", Type: sql.Int32, Source: ddl.TableBoards},
		{Name: "data", Type: sql.Blob, Nullable: true, Source: ddl.TableBoards},
	})
	// unique index is broken
	err := boardsTable.CreateIndex(sql.NewEmptyContext(), "uniq_board", sql.IndexUsing_BTree, sql.IndexConstraint_Unique, []sql.IndexColumn{
		{Name: "app_id", Length: 100}, {Name: "room_id", Length: 100}, {Name: "board_id"},
	}, "")
	if err != nil {
		panic(err)
	}
	db.AddTable(ddl.TableBoards, boardsTable)

	pagesTable := memory.NewTable(ddl.TablePages, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true, Source: ddl.TablePages},
		{Name: "app_id", Type: sql.Text, Source: ddl.TablePages},
		{Name: "room_id", Type: sql.Text, Source: ddl.TablePages},
		{Name: "board_id", Type: sql.Int32, Source: ddl.TablePages},
		{Name: "page_id", Type: sql.Text, Source: ddl.TablePages},
		{Name: "pre_page_id", Type: sql.Text, Source: ddl.TablePages},
		{Name: "extra", Type: sql.Text, Nullable: true, Source: ddl.TablePages},
		{Name: "control_count", Type: sql.Int32, Source: ddl.TablePages},
		{Name: "data", Type: sql.Blob, Source: ddl.TablePages},
		{Name: "deleted_at", Type: sql.Timestamp, Nullable: true, Source: ddl.TablePages},
		{Name: "del_timestamp", Type: sql.Int64, Nullable: true, Source: ddl.TablePages},
	})
	err = pagesTable.CreateIndex(sql.NewEmptyContext(), "uniq_page", sql.IndexUsing_BTree, sql.IndexConstraint_Unique, []sql.IndexColumn{
		{Name: "app_id"}, {Name: "room_id"}, {Name: "board_id"}, {Name: "page_id"},
	}, "")
	if err != nil {
		panic(err)
	}
	err = pagesTable.CreateIndex(sql.NewEmptyContext(), "uniq_pre_page", sql.IndexUsing_BTree, sql.IndexConstraint_Unique, []sql.IndexColumn{
		{Name: "app_id"}, {Name: "room_id"}, {Name: "board_id"}, {Name: "del_timestamp"},
	}, "")
	if err != nil {
		panic(err)
	}
	db.AddTable(ddl.TablePages, pagesTable)

	db.AddTable(ddl.TableReplays, memory.NewTable(ddl.TableReplays, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true},
		{Name: "app_id", Type: sql.Text},
		{Name: "account_id", Type: sql.Text},
		{Name: "room_id", Type: sql.Text},
		{Name: "board_id", Type: sql.Int32},
		{Name: "bucket", Type: sql.Text},
		{Name: "region", Type: sql.Text},
		{Name: "file_name", Type: sql.Text},
		{Name: "start_time", Type: sql.Uint64},
		{Name: "end_time", Type: sql.Uint64},
		{Name: "max_id", Type: sql.Int64},
		{Name: "action_count", Type: sql.Int32},
		{Name: "created_at", Type: sql.Timestamp},
	}))

	consoleConfigTable := memory.NewTable(ddl.TableConsoleConfigs, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true, Source: ddl.TableConsoleConfigs},
		{Name: "app_id", Type: sql.Text, Source: ddl.TableConsoleConfigs},
		{Name: "parent_category", Type: sql.Int32, Source: ddl.TableConsoleConfigs},
		{Name: "category", Type: sql.Int32, Source: ddl.TableConsoleConfigs},
		{Name: "space_type", Type: sql.Int32, Source: ddl.TableConsoleConfigs},
		{Name: "space", Type: sql.Text, Source: ddl.TableConsoleConfigs},
		{Name: "account_id", Type: sql.Text, Source: ddl.TableConsoleConfigs},
		{Name: "region", Type: sql.Text, Nullable: true, Source: ddl.TableConsoleConfigs},
		{Name: "deleted", Type: sql.Int32, Source: ddl.TableConsoleConfigs},
		{Name: "created_at", Type: sql.Timestamp, Source: ddl.TableConsoleConfigs},
		{Name: "updated_at", Type: sql.Timestamp, Source: ddl.TableConsoleConfigs},
	})
	err = consoleConfigTable.CreateIndex(sql.NewEmptyContext(), "uniq_config", sql.IndexUsing_BTree, sql.IndexConstraint_Unique, []sql.IndexColumn{
		{Name: "app_id"}, {Name: "category"},
	}, "")
	if err != nil {
		panic(err)
	}
	err = consoleConfigTable.CreateIndex(sql.NewEmptyContext(), "uniq_parent", sql.IndexUsing_BTree, sql.IndexConstraint_None, []sql.IndexColumn{
		{Name: "app_id"}, {Name: "parent_category"},
	}, "")
	if err != nil {
		panic(err)
	}
	db.AddTable(ddl.TableConsoleConfigs, consoleConfigTable)

	consoleSwitchTable := memory.NewTable(ddl.TableConsoleSwitchs, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true, Source: ddl.TableConsoleSwitchs},
		{Name: "app_id", Type: sql.Text, Source: ddl.TableConsoleSwitchs},
		{Name: "category", Type: sql.Int32, Source: ddl.TableConsoleSwitchs},
		{Name: "status", Type: sql.Int32, Source: ddl.TableConsoleSwitchs},
		{Name: "created_at", Type: sql.Timestamp, Source: ddl.TableConsoleSwitchs},
		{Name: "updated_at", Type: sql.Timestamp, Source: ddl.TableConsoleSwitchs},
	})
	err = consoleSwitchTable.CreateIndex(sql.NewEmptyContext(), "uniq_switch", sql.IndexUsing_BTree, sql.IndexConstraint_Unique, []sql.IndexColumn{
		{Name: "app_id"}, {Name: "category"},
	}, "")
	if err != nil {
		panic(err)
	}
	db.AddTable(ddl.TableConsoleSwitchs, consoleSwitchTable)

	roomsTable := memory.NewTable(ddl.TableRooms, sql.Schema{
		{Name: "id", Type: sql.Int64, AutoIncrement: true, Source: ddl.TableRooms},
		{Name: "app_id", Type: sql.Text, Source: ddl.TableRooms},
		{Name: "room_id", Type: sql.Text, Source: ddl.TableRooms},
		{Name: "active_board_id", Type: sql.Int32, Source: ddl.TableRooms},
		{Name: "status", Type: sql.Int32, Source: ddl.TableRooms},
		{Name: "created_at", Type: sql.Timestamp, Source: ddl.TableRooms},
		{Name: "updated_at", Type: sql.Timestamp, Source: ddl.TableRooms},
		{Name: "data", Type: sql.Blob, Nullable: true, Source: ddl.TableRooms},
	})
	// unique index is broken
	err = roomsTable.CreateIndex(sql.NewEmptyContext(), "uniq_room", sql.IndexUsing_BTree, sql.IndexConstraint_Unique, []sql.IndexColumn{
		{Name: "app_id", Length: 100}, {Name: "room_id", Length: 100},
	}, "")
	if err != nil {
		panic(err)
	}
	db.AddTable(ddl.TableRooms, roomsTable)
	return db
}

func NewDatabase() *gorm.DB {
	db, err := gorm.Open(bytedgorm.MySQL("", "").With(func(config *bytedgorm.DBConfig) {
		config.DSN = "user:password@tcp(localhost:" + cast.ToString(port) + ")/test?parseTime=true&loc=Local"
	}), &gorm.Config{
		CreateBatchSize: 30,
		Logger: bytedgorm.Logger{
			LogLevel:     logger.Info,
			CompatLogger: log.V1,
		},
	})
	if err != nil {
		panic(err)
	}
	return db
}
